import re
import json
from datetime import datetime

from django.shortcuts import render
from openai import OpenAI
from pymilvus import connections, FieldSchema, DataType, CollectionSchema, Collection
from pymilvus.orm import utility
from rest_framework.response import Response
from rest_framework.views import APIView

from home.models import Jobposting

# Elasticsearch相关导入（如果没有安装elasticsearch，请运行: pip install elasticsearch）
try:
    from elasticsearch import Elasticsearch
    from elasticsearch.helpers import bulk
    ES_AVAILABLE = True
except ImportError:
    print("⚠️ Elasticsearch未安装，请运行: pip install elasticsearch")
    ES_AVAILABLE = False


def write_to_milvus(job_list):
    """
    💾 Milvus向量数据库写入函数 - RAG系统的核心存储模块
    
    核心功能：
    1. 建立与本地Milvus向量数据库的连接
    2. 创建针对招聘场景优化的集合Schema
    3. 建立高效的向量索引以支持快速检索
    4. 批量插入职位向量数据并确保数据完整性
    5. 优化集合配置以支持实时语义搜索
    
    技术架构：
    - 数据库：Milvus 2.x 分布式向量数据库
    - 索引：IVF_FLAT + L2距离，平衡检索速度与精度
    - 向量维度：1024维，支持高质量语义相似度计算
    - 字段设计：职位元数据 + 向量表示的混合存储
    
    Args:
        job_list (list): 包含向量化后职位数据的列表
            每个job包含：id, title, money, description, chunks, embeddings等字段
            
    处理流程：
    连接数据库 → 创建Schema → 建立索引 → 数据验证 → 批量插入 → 性能优化
    """
    
    # 🔗 Step 1: 建立Milvus数据库连接
    print("🔗 Step 1: 连接Milvus向量数据库...")
    try:
        connections.connect(
            alias="default",
            host="localhost",  # 本地Milvus服务地址
            port="19530"       # Milvus默认端口
        )
        print("   ✅ 成功连接到本地Milvus服务 (localhost:19530)")
    except Exception as e:
        raise ConnectionError(f"❌ Milvus连接失败: {str(e)}")
    
    # 🏗️ Step 2: 设计集合Schema - 针对招聘场景优化
    print("🏗️ Step 2: 创建招聘职位向量集合Schema...")
    
    fields = [
        # 🔑 主键：职位向量唯一标识符 (job_id_chunk_index格式)
        FieldSchema(name="id", dtype=DataType.VARCHAR, max_length=100, is_primary=True),
        
        # 🎯 核心向量字段：1024维高质量语义向量
        FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024),
        
        # 📄 文本字段：支持结果展示和二次筛选（优化长度以支持完整内容）
        FieldSchema(name="full_text", dtype=DataType.VARCHAR, max_length=8000),   # 完整职位描述，支持超长内容
        FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=300),        # 职位名称，支持详细标题
        FieldSchema(name="money", dtype=DataType.VARCHAR, max_length=150),        # 薪资信息，支持复杂薪资结构
        FieldSchema(name="description", dtype=DataType.VARCHAR, max_length=5000), # 职位描述，大幅增加到5000字符
        
        # 🏢 新增字段：公司和地理位置信息（支持筛选和地域检索）
        FieldSchema(name="company_name", dtype=DataType.VARCHAR, max_length=200), # 公司名称，支持企业筛选
        FieldSchema(name="city_name", dtype=DataType.VARCHAR, max_length=100),    # 城市名称，支持地域筛选
    ]
    
    print("   📋 Schema字段设计（增强版 - 新增公司城市字段）:")
    print("      - id: 主键(100字符)，支持job到chunk的精确映射")
    print("      - embedding: 1024维向量，核心语义表示")
    print("      - full_text: 完整描述(8000字符)，支持超长职位详情")
    print("      - title: 职位名称(300字符)，支持详细职位标题")
    print("      - money: 薪资信息(150字符)，支持复杂薪资结构")
    print("      - description: 职位描述(5000字符)，大幅扩容支持完整描述")
    print("      - company_name: 公司名称(200字符)，支持企业筛选和品牌检索")
    print("      - city_name: 城市名称(100字符)，支持地域筛选和位置检索")

    # 🎨 创建集合Schema配置
    schema = CollectionSchema(
        fields=fields,
        description="🏢 智能招聘职位向量集合 - 支持语义搜索和精准匹配",
        enable_dynamic_field=False  # 禁用动态字段，确保数据结构一致性
    )
    print("   ✅ Schema创建完成")

    # 🗂️ Step 3: 集合管理 - 确保数据新鲜度
    collection_name = "job_list_optimized"  # 使用新的集合名称区分优化版本
    print(f"🗂️  Step 3: 管理集合 '{collection_name}'...")

    # 检查并处理已存在的集合
    if utility.has_collection(collection_name, using="default"):
        print(f"   🔄 发现已存在集合，删除旧数据确保新鲜度...")
        utility.drop_collection(collection_name, using="default")
        print(f"   ✅ 已删除旧集合: {collection_name}")

    # 创建新的集合实例
    collection = Collection(name=collection_name, schema=schema, using="default")
    print(f"   🎉 成功创建新集合: {collection_name}")

    # ⚡ Step 4: 创建高效向量索引
    print("⚡ Step 4: 构建向量索引以支持快速检索...")

    index_params = {
        "index_type": "IVF_FLAT",    # 倒排文件索引，适合中等规模数据(<100万向量)
        "metric_type": "L2",         # L2欧氏距离，适合语义相似度计算
        "params": {"nlist": 128}     # 聚类中心数量，影响查询精度和速度平衡
    }
    
    collection.create_index(
        field_name="embedding",
        index_params=index_params
    )
    
    print("   🎯 索引配置:")
    print("      - 类型: IVF_FLAT (倒排文件)")
    print("      - 距离: L2欧氏距离")  
    print("      - 聚类: 128个中心点")
    print("   ✅ 向量索引创建完成")

    
    # 📦 Step 5: 数据预处理和验证
    print("📦 Step 5: 准备并验证向量数据...")
    
    if not job_list:
        print("   ⚠️  警告：job_list为空，跳过数据插入")
        return
    
    print(f"   📊 数据源：{len(job_list)}个职位")
    
    # 🔍 数据完整性验证
    print("   🔍 执行数据完整性检查...")
    valid_jobs = 0
    for job in job_list:
        job_id = job.get('id', 'unknown')
        
        # 检查必要字段
        if 'embeddings' not in job or not job['embeddings']:
            print(f"   ⚠️  职位 {job_id} 缺少embeddings字段，跳过")
            continue
            
        if 'chunks' in job and len(job['embeddings']) != len(job.get('chunks', [])):
            print(f"   ⚠️  职位 {job_id} 的embeddings与chunks数量不匹配")
            continue
            
        valid_jobs += 1
    
    print(f"   ✅ 验证完成：{valid_jobs}/{len(job_list)}个职位数据有效")

    # 🔄 构建批量插入数据结构
    print("   🔄 构建Milvus插入数据结构...")
    
    ids = []             # 主键列表
    embeddings = []      # 向量列表
    full_texts = []      # 完整文本列表
    titles = []          # 职位名称列表
    moneys = []          # 薪资列表
    descriptions = []    # 描述列表
    company_names = []   # 🆕 公司名称列表
    city_names = []      # 🆕 城市名称列表
    
    for job in job_list:
        if 'embeddings' not in job or not job['embeddings']:
            continue  # 跳过无效数据
            
        job_id = job.get('id', '')
        job_embeddings = job.get('embeddings', [])
        job_full_text = job.get('full_text', '')
        job_title = job.get('title', '')
        job_money = job.get('money', '')
        job_description = job.get('description', '')
        
        # 🎯 为每个embedding创建独立的向量记录
        # 这支持一个职位的多个语义chunk进行独立检索和排序
        for i, embedding in enumerate(job_embeddings):
            unique_id = f"{job_id}_{i}"  # 生成唯一标识符
            
            # 🔧 文本长度控制 - 确保符合扩展后的Schema限制
            safe_full_text = job_full_text[:7900] if len(job_full_text) > 7900 else job_full_text
            safe_title = job_title[:290] if len(job_title) > 290 else job_title  
            safe_money = job_money[:140] if len(job_money) > 140 else job_money
            safe_description = job_description[:4900] if len(job_description) > 4900 else job_description
            
            # 🏢 新增字段的长度控制
            job_company_name = job.get('company_name', '')
            job_city_name = job.get('city_name', '')
            safe_company_name = job_company_name[:190] if len(job_company_name) > 190 else job_company_name
            safe_city_name = job_city_name[:90] if len(job_city_name) > 90 else job_city_name
            
            # 📝 如果发生截断，添加提示（现在截断的可能性大大降低）
            if len(job_full_text) > 7900:
                safe_full_text = safe_full_text + "..."
                print(f"   ⚠️  职位 {job_id} 的full_text超长({len(job_full_text)}字符)，已截断到7900字符")
            
            if len(job_description) > 4900:
                safe_description = safe_description + "..."
                print(f"   ⚠️  职位 {job_id} 的description超长({len(job_description)}字符)，已截断到4900字符")
            
            if len(job_title) > 290:
                safe_title = safe_title + "..."
                print(f"   ⚠️  职位 {job_id} 的title超长({len(job_title)}字符)，已截断到290字符")
            
            if len(job_company_name) > 190:
                safe_company_name = safe_company_name + "..."
                print(f"   ⚠️  职位 {job_id} 的company_name超长({len(job_company_name)}字符)，已截断到190字符")
            
            ids.append(unique_id)
            embeddings.append(embedding)
            full_texts.append(safe_full_text)
            titles.append(safe_title)
            moneys.append(safe_money)
            descriptions.append(safe_description)
            company_names.append(safe_company_name)  # 🆕 公司名称
            city_names.append(safe_city_name)        # 🆕 城市名称
    
    print(f"   📈 生成插入数据：{len(ids)}条向量记录")
    
    # 🚀 Step 6: 批量插入向量数据
    print("🚀 Step 6: 执行批量数据插入...")
    
    if not ids:
        print("   ⚠️  没有有效数据可插入")
        return
    
    # 📋 按Schema字段顺序组织数据（新增公司城市字段）
    insert_data = [
        ids,              # id字段 (VARCHAR主键)
        embeddings,       # embedding字段 (FLOAT_VECTOR)
        full_texts,       # full_text字段 (VARCHAR)
        titles,           # title字段 (VARCHAR)
        moneys,           # money字段 (VARCHAR)
        descriptions,     # description字段 (VARCHAR)
        company_names,    # 🆕 company_name字段 (VARCHAR)
        city_names        # 🆕 city_name字段 (VARCHAR)
    ]
    
    try:
        print(f"   🔄 正在插入{len(ids)}条向量记录...")
        
        # 执行批量插入操作
        result = collection.insert(insert_data)
        print(f"   ✅ 批量插入成功，主键数量：{len(result.primary_keys)}")
        
        # 💾 数据持久化：刷新确保数据写入磁盘
        print("   💾 执行数据持久化...")
        collection.flush()
        print("   ✅ 数据已刷新到磁盘")
        
        # ⚡ 性能优化：加载集合到内存支持快速检索
        print("   ⚡ 加载集合到内存...")
        collection.load()
        print("   ✅ 集合已加载，支持高速向量检索")
        
        # 📊 验证插入结果
        print(f"   🔗 主键样例：{result.primary_keys[:3]}...")
        
    except Exception as e:
        error_msg = f"Milvus数据插入失败: {str(e)}"
        print(f"   ❌ {error_msg}")
        raise RuntimeError(error_msg)
    
    # 📊 Step 7: 最终统计和验证
    print("📊 Step 7: 生成最终统计报告...")
    
    try:
        # 获取集合统计信息
        entity_count = collection.num_entities
        
        print("   🎉 Milvus向量数据库存储完成！")
        print("   📈 最终统计:")
        print(f"      - 集合名称: {collection_name}")
        print(f"      - 总向量数: {entity_count}")
        print(f"      - 向量维度: 1024")
        print(f"      - 索引类型: IVF_FLAT")
        print(f"      - 距离度量: L2欧氏距离")
        print("   ✅ 系统已就绪，支持语义搜索和智能推荐！")
        
    except Exception as e:
        print(f"   ⚠️  统计信息获取失败: {str(e)}")
        print("   💡 数据已成功插入，统计功能异常不影响核心功能")





def optimized_job_chunking(job_data):
    """
    🎯 优化的职位信息分块策略 - 基于语义单元的混合策略
    
    核心思路：
    1. 将职位信息按照语义相关性分组，而不是简单按字符长度切分
    2. 每个chunk保持语义完整性，提高向量表示的准确性
    3. 适度控制chunk数量（3-4个），平衡精度和召回率
    4. 针对用户常见搜索模式（技能、薪资、地点）进行优化分组
    
    Args:
        job_data (dict): 包含职位完整信息的字典
            - title: 职位名称
            - description: 职位描述  
            - money: 薪资信息
            - working: 工作经验要求
            - education: 学历要求
            - city: 工作城市
            - full_text: 完整文本（用于兜底）
    
    Returns:
        list: 语义完整的文本块列表，每个chunk都是独立的语义单元
        
    设计理念：
    - 优先保证语义完整性，避免切断相关信息
    - 针对招聘场景的特点进行专门优化
    - 支持多维度检索（职位技能、薪资待遇、地理位置等）
    """
    print(f"正在处理职位: {job_data.get('title', 'Unknown')}")
    
    chunks = []
    
    # 🎯 Chunk 1: 核心职位信息（高权重 - 最常被搜索）
    # 包含职位名称和核心职责描述，这是用户最关心的技能匹配部分
    title = job_data.get('title', '').strip()
    description = job_data.get('description', '').strip()
    
    if title and description:
        # 如果职位描述过长，进行智能截断但保持语义完整
        if len(description) > 150:
            # 找到合适的截断点（句号、分号、逗号）
            truncate_pos = 150
            for delimiter in ['。', ';', '；', ',', '，']:
                pos = description.find(delimiter, 100, 150)
                if pos != -1:
                    truncate_pos = pos + 1
                    break
            core_description = description[:truncate_pos].strip()
        else:
            core_description = description
        
        core_chunk = f"职位：{title} 职责：{core_description}"
        chunks.append(core_chunk)
        print(f"  -> 核心信息chunk: {len(core_chunk)}字符")
        
        # 如果描述很长，将剩余部分作为补充技能要求
        if len(description) > 150:
            remaining_description = description[truncate_pos:].strip()
            if remaining_description:
                skill_chunk = f"技能要求：{remaining_description}"
                chunks.append(skill_chunk)
                print(f"  -> 技能要求chunk: {len(skill_chunk)}字符")
    
    # 🎯 Chunk 2: 岗位要求和条件（中权重）
    # 合并学历、经验等硬性条件，方便筛选匹配
    education = job_data.get('education', '').strip()
    working_exp = job_data.get('working', '')
    
    # 将working数字转换为可读文本
    working_map = {
        0: "经验不限", 1: "在校生", 2: "应届生", 
        3: "1-3年", 4: "3-5年", 5: "5年以上"
    }
    working_text = working_map.get(working_exp, "经验不限") if isinstance(working_exp, int) else str(working_exp)
    
    requirements_parts = []
    if education and education != "不限":
        requirements_parts.append(f"学历要求：{education}")
    if working_text and working_text != "经验不限":
        requirements_parts.append(f"工作经验：{working_text}")
    
    if requirements_parts:
        requirements_chunk = " ".join(requirements_parts)
        chunks.append(requirements_chunk)
        print(f"  -> 岗位要求chunk: {len(requirements_chunk)}字符")
    
    # 🎯 Chunk 3: 薪资和地理位置（高权重 - 经常被联合查询）
    # 这两个维度经常一起被搜索，如"北京 15-20K"
    money = job_data.get('money', '').strip()
    
    # 🏢 优先使用新的city_name字段，向下兼容city对象
    city_info = job_data.get('city_name', '')  # 优先使用提取好的城市名
    if not city_info:
        # 向下兼容：处理Django对象
        city = job_data.get('city', '')
        if hasattr(city, 'name'):  # Django model对象
            city_info = city.name
        elif isinstance(city, str):
            city_info = city
    
    location_salary_parts = []
    if money:
        location_salary_parts.append(f"薪资：{money}")
    if city_info:
        location_salary_parts.append(f"工作地点：{city_info}")
    
    if location_salary_parts:
        location_salary_chunk = " ".join(location_salary_parts)
        chunks.append(location_salary_chunk)
        print(f"  -> 薪资地点chunk: {len(location_salary_chunk)}字符")
    
    # 🛡️ 兜底策略：如果上述分块失败，使用原始full_text
    if not chunks:
        full_text = job_data.get('full_text', '').strip()
        if full_text:
            # 使用改进的字符级分块作为备选方案
            fallback_chunks = improved_character_split(full_text, max_chunk_size=200)
            chunks.extend(fallback_chunks)
            print(f"  -> 使用兜底策略，生成{len(fallback_chunks)}个chunk")
    
    # 📊 质量验证：确保每个chunk都有实际内容
    valid_chunks = [chunk.strip() for chunk in chunks if chunk.strip() and len(chunk.strip()) > 10]
    
    print(f"  ✅ 最终生成{len(valid_chunks)}个高质量语义chunk")
    return valid_chunks


def improved_character_split(text, max_chunk_size=200):
    """
    🔧 改进的字符级文本分割（兜底方案）
    
    相比原来的simple_sentence_split，这个函数：
    1. 增大chunk_size到200字符，保证语义相对完整
    2. 优先在句子边界分割
    3. 避免在词语中间断开

    Args:
        text (str): 需要切分的文本
        max_chunk_size (int): 最大块大小，默认200字符

    Returns:
        list: 切分后的文本块列表
    """
    if not text or len(text.strip()) == 0:
        return []
    
    text = text.strip()
    
    # 如果文本本身就不长，直接返回
    if len(text) <= max_chunk_size:
        return [text]
    
    chunks = []
    current_pos = 0
    
    while current_pos < len(text):
        # 确定当前chunk的结束位置
        end_pos = min(current_pos + max_chunk_size, len(text))
        
        # 如果不是文本末尾，尝试在合适的位置断开
        if end_pos < len(text):
            # 优先在句子边界断开
            sentence_delimiters = ['。', '！', '？', '.', '!', '?']
            best_break = -1
            
            # 在后半段寻找句子结束符
            search_start = max(current_pos, end_pos - 50)
            for i in range(end_pos - 1, search_start - 1, -1):
                if text[i] in sentence_delimiters:
                    best_break = i + 1
                    break
            
            # 如果找不到句子边界，在词语边界断开
            if best_break == -1:
                word_delimiters = [' ', '，', ',', ';', '；', '、']
                for i in range(end_pos - 1, search_start - 1, -1):
                    if text[i] in word_delimiters:
                        best_break = i + 1
                        break
            
            # 使用找到的断点，否则强制在max_chunk_size处断开
            if best_break != -1:
                end_pos = best_break
        
        # 提取当前chunk
        chunk = text[current_pos:end_pos].strip()
        if chunk:
            chunks.append(chunk)
        
        current_pos = end_pos
        
        # 防止无限循环
        if current_pos == end_pos - 1:
            current_pos += 1

    return chunks


def text_embeddings(chunks):
    """
    🤖 文本向量化处理函数 - 将文本块转换为高维向量表示
    
    核心思路：
    1. 使用阿里云通义千问的text-embedding-v4模型进行向量化
    2. 批量处理提高API调用效率，降低网络开销
    3. 针对招聘领域进行参数优化，提升向量质量
    4. 确保向量维度一致性，支持后续相似度计算
    
    Args:
        chunks (list): 待向量化的文本块列表，由optimized_job_chunking生成
    
    Returns:
        list: 向量列表，每个向量为1024维的浮点数数组
        
    技术细节：
    - 模型：text-embedding-v4（支持中文，语义理解能力强）
    - 维度：1024维（平衡精度与存储效率的最佳选择）
    - 批处理：每次最多处理10个chunk，避免API限制
    - 领域优化：设置user="job"增强招聘领域相关性
    """
    if not chunks:
        print("⚠️  警告：chunks列表为空，返回空向量列表")
        return []
    
    # 🔑 API配置 - 使用阿里云通义千问嵌入服务
    api_key = "sk-6434b554122c4b049ceb805d703f695b"
    if not api_key:
        raise ValueError("❌ API密钥未配置，请设置DASHSCOPE_API_KEY环境变量")

    client = OpenAI(
        api_key=api_key,
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
    )

    # 📊 批处理配置 - 优化API调用效率
    batch_size = 10  # 通义千问API的最大批处理限制
    embeddings = []
    total_chunks = len(chunks)
    
    print(f"🚀 开始向量化处理，共{total_chunks}个文本块")
    
    # 🔄 分批处理避免API限制和超时
    for i in range(0, total_chunks, batch_size):
        batch = chunks[i:i + batch_size]
        batch_num = i // batch_size + 1
        total_batches = (total_chunks + batch_size - 1) // batch_size
        
        print(f"📦 处理批次 {batch_num}/{total_batches}：{len(batch)}个chunk")
        
        try:
            # 🎯 调用embedding API
            response = client.embeddings.create(
                model="text-embedding-v4",           # 高质量中文embedding模型
                input=batch,                         # 当前批次的文本列表
                dimensions=1024,                     # 1024维向量（招聘场景最佳平衡点）
                encoding_format="float",             # 浮点数格式，支持相似度计算
                user="job"                          # 招聘领域标识，增强领域相关性
            )
            
            # ✅ 提取向量并保持与输入顺序一致
            batch_embeddings = [item.embedding for item in response.data]
            embeddings.extend(batch_embeddings)
            
            print(f"  ✅ 批次{batch_num}处理完成，生成{len(batch_embeddings)}个向量")
            
        except Exception as e:
            print(f"❌ 批次{batch_num}处理失败: {str(e)}")
            # 🛡️ 错误处理：为失败的batch生成零向量占位
            zero_embeddings = [[0.0] * 1024 for _ in batch]
            embeddings.extend(zero_embeddings)
            print(f"  🔧 已生成零向量占位，确保数据完整性")
    
    # 📈 最终验证
    print(f"🎉 向量化完成！输入{total_chunks}个chunk，输出{len(embeddings)}个向量")
    
    # 🔍 质量检查：确保向量维度正确
    if embeddings:
        first_vector_dim = len(embeddings[0])
        print(f"📏 向量维度验证：{first_vector_dim}维")
        
        # 检查维度一致性
        for i, vec in enumerate(embeddings):
            if len(vec) != 1024:
                print(f"⚠️  警告：向量{i}维度异常 - 期望1024维，实际{len(vec)}维")
    
    return embeddings


def connect_to_elasticsearch():
    """
    🔗 连接到Elasticsearch服务器
    
    Returns:
        Elasticsearch: ES客户端对象，失败时返回None
    """
    if not ES_AVAILABLE:
        print("❌ Elasticsearch库未安装，无法连接")
        return None
    
    try:
        # 连接到本地Elasticsearch服务器（针对ES 8.x优化）
        try:
            # ES 8.x版本连接（禁用SSL验证，适用于开发环境）
            es = Elasticsearch(
                hosts=['http://localhost:9200'],
                verify_certs=False,
                ssl_show_warn=False,
                request_timeout=30,
                max_retries=3,
                retry_on_timeout=True
            )
        except Exception as e1:
            print(f"ES 8.x连接尝试失败: {e1}")
            try:
                # 备用连接方式：简化配置
                es = Elasticsearch(
                    ['http://localhost:9200'],
                    verify_certs=False,
                    ssl_show_warn=False
                )
            except Exception as e2:
                print(f"备用连接失败: {e2}")
                # 最后尝试：ES 7.x兼容方式
                es = Elasticsearch(
                    [{'host': 'localhost', 'port': 9200, 'scheme': 'http'}]
                )
        
        # 测试连接
        if es.ping():
            print("✅ Elasticsearch连接成功")
            return es
        else:
            print("❌ Elasticsearch连接失败：服务器无响应")
            return None
            
    except Exception as e:
        print(f"❌ Elasticsearch连接失败: {str(e)}")
        print("💡 请确保Elasticsearch服务已启动（默认端口9200）")
        return None


def create_job_index(es_client):
    """
    🏗️ 创建招聘职位的Elasticsearch索引
    
    Args:
        es_client: Elasticsearch客户端对象
        
    Returns:
        bool: 创建成功返回True，失败返回False
    """
    if not es_client:
        return False
    
    index_name = "job_positions"
    
    try:
        # 检查索引是否已存在
        if es_client.indices.exists(index=index_name):
            print(f"📂 索引 '{index_name}' 已存在")
            return True
        
        # 定义索引映射（Schema）
        index_mapping = {
            "mappings": {
                "properties": {
                    "job_id": {
                        "type": "keyword"  # 精确匹配
                    },
                    "title": {
                        "type": "text",
                        "analyzer": "ik_max_word",  # 中文分词器（如果安装了IK插件）
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "company_name": {
                        "type": "text",
                        "analyzer": "ik_max_word",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "city_name": {
                        "type": "keyword"
                    },
                    "money": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword"
                            }
                        }
                    },
                    "description": {
                        "type": "text",
                        "analyzer": "ik_max_word"
                    },
                    "education": {
                        "type": "keyword"
                    },
                    "working": {
                        "type": "integer"
                    },
                    "working_text": {
                        "type": "keyword"
                    },
                    "type": {
                        "type": "integer"
                    },
                    "type_text": {
                        "type": "keyword"
                    },
                    "label": {
                        "type": "text",
                        "analyzer": "ik_max_word"
                    },
                    "browse_number": {
                        "type": "integer"
                    },
                    "collect_number": {
                        "type": "integer"
                    },
                    "deliver_number": {
                        "type": "integer"
                    },
                    "full_text": {
                        "type": "text",
                        "analyzer": "ik_max_word"
                    },
                    "created_at": {
                        "type": "date"
                    },
                    "updated_at": {
                        "type": "date"
                    }
                }
            },
            "settings": {
                "number_of_shards": 1,
                "number_of_replicas": 0,
                "analysis": {
                    "analyzer": {
                        "default": {
                            "type": "standard"
                        }
                    }
                }
            }
        }
        
        # 创建索引（兼容ES 8.x）
        try:
            # ES 8.x版本API
            es_client.indices.create(
                index=index_name,
                mappings=index_mapping["mappings"],
                settings=index_mapping["settings"]
            )
        except TypeError:
            try:
                # 旧版本兼容
                es_client.indices.create(index=index_name, body=index_mapping)
            except Exception:
                # 最简单的创建方式
                es_client.indices.create(index=index_name)
        print(f"✅ 成功创建索引 '{index_name}'")
        return True
        
    except Exception as e:
        print(f"❌ 创建索引失败: {str(e)}")
        return False


def sync_jobs_to_elasticsearch(job_list):
    """
    📤 将职位数据同步到Elasticsearch
    
    Args:
        job_list: 职位数据列表
        
    Returns:
        dict: 同步结果统计
    """
    print("📤 开始同步数据到Elasticsearch...")
    
    # 连接ES
    es_client = connect_to_elasticsearch()
    if not es_client:
        return {"status": "error", "message": "无法连接到Elasticsearch"}
    
    # 创建索引
    if not create_job_index(es_client):
        return {"status": "error", "message": "索引创建失败"}
    
    index_name = "job_positions"
    
    # 工作经验映射
    working_choices = {
        0: "经验不限",
        1: "在校生", 
        2: "应届生",
        3: "1-3年",
        4: "3-5年",
        5: "5年以上"
    }
    
    # 职位类型映射
    type_choices = {
        0: "不限",
        1: "兼职",
        2: "全职"
    }
    
    try:
        # 准备批量插入的数据
        actions = []
        success_count = 0
        error_count = 0
        
        for index, job_info in enumerate(job_list):
            try:
                # 构建ES文档
                doc = {
                    "job_id": job_info.get("id", str(index)),
                    "title": job_info.get("title", ""),
                    "company_name": job_info.get("company_name", ""),
                    "city_name": job_info.get("city_name", ""),
                    "money": job_info.get("money", ""),
                    "description": job_info.get("description", ""),
                    "education": job_info.get("education", ""),
                    "working": job_info.get("working", 0),
                    "working_text": working_choices.get(job_info.get("working", 0), "经验不限"),
                    "type": job_info.get("type", 0),
                    "type_text": type_choices.get(job_info.get("type", 0), "不限"),
                    "label": job_info.get("label", job_info.get("title", "")),
                    "browse_number": job_info.get("browse_number", 0),
                    "collect_number": job_info.get("collect_number", 0),
                    "deliver_number": job_info.get("deliver_number", 0),
                    "full_text": job_info.get("full_text", ""),
                    "created_at": datetime.now().isoformat(),
                    "updated_at": datetime.now().isoformat()
                }
                
                # 添加到批量操作
                action = {
                    "_index": index_name,
                    "_id": job_info.get("id", str(index)),
                    "_source": doc
                }
                actions.append(action)
                
            except Exception as e:
                print(f"⚠️ 处理职位{index}时出错: {str(e)}")
                error_count += 1
                continue
        
        # 批量插入到ES
        if actions:
            print(f"📊 准备批量插入{len(actions)}条记录到Elasticsearch...")
            
            # 使用bulk API批量插入
            success, failed = bulk(
                es_client,
                actions,
                index=index_name,
                chunk_size=500,  # 每批处理500条
                request_timeout=60  # 超时时间60秒
            )
            
            success_count = success
            error_count += len(failed) if failed else 0
            
            print(f"✅ 批量插入完成：成功{success_count}条，失败{error_count}条")
            
            # 刷新索引确保数据可搜索
            es_client.indices.refresh(index=index_name)
            print("🔄 索引刷新完成，数据已可搜索")
            
        else:
            print("⚠️ 没有有效的数据可以插入")
        
        # 返回统计结果
        result = {
            "status": "success",
            "total_jobs": len(job_list),
            "success_count": success_count,
            "error_count": error_count,
            "index_name": index_name,
            "message": f"成功同步{success_count}个职位到Elasticsearch"
        }
        
        print(f"🎉 Elasticsearch同步完成！{result['message']}")
        return result
        
    except Exception as e:
        error_msg = f"Elasticsearch同步失败: {str(e)}"
        print(f"❌ {error_msg}")
        return {"status": "error", "message": error_msg}


def search_jobs_in_elasticsearch(query_text, size=10):
    """
    🔍 在Elasticsearch中搜索职位
    
    Args:
        query_text: 搜索查询文本
        size: 返回结果数量
        
    Returns:
        dict: 搜索结果
    """
    es_client = connect_to_elasticsearch()
    if not es_client:
        return {"status": "error", "message": "无法连接到Elasticsearch"}
    
    index_name = "job_positions"
    
    try:
        # 构建搜索查询
        search_body = {
            "query": {
                "multi_match": {
                    "query": query_text,
                    "fields": [
                        "title^3",          # 职位标题权重最高
                        "description^2",    # 职位描述权重次之
                        "company_name^2",   # 公司名称权重较高
                        "label",            # 职位标签
                        "full_text"         # 全文搜索
                    ],
                    "type": "best_fields",
                    "fuzziness": "AUTO"     # 自动模糊匹配
                }
            },
            "highlight": {
                "fields": {
                    "title": {},
                    "description": {},
                    "company_name": {}
                }
            },
            "sort": [
                {"_score": {"order": "desc"}}  # 按相关性排序
            ]
        }
        
        # 执行搜索（兼容ES 8.x）
        try:
            # ES 8.x版本API
            response = es_client.search(
                index=index_name,
                size=size,
                query=search_body["query"],
                highlight=search_body.get("highlight"),
                sort=search_body.get("sort")
            )
        except TypeError:
            try:
                # 旧版本兼容
                response = es_client.search(
                    index=index_name,
                    body=search_body,
                    size=size
                )
            except Exception:
                # 最简单的搜索
                response = es_client.search(
                    index=index_name,
                    q=search_body["query"]["multi_match"]["query"],
                    size=size
                )
        
        # 解析结果
        hits = response['hits']['hits']
        
        # 兼容不同版本的total字段格式
        total_info = response['hits']['total']
        if isinstance(total_info, dict):
            total = total_info.get('value', 0)
        else:
            total = total_info
        
        results = []
        for hit in hits:
            result = {
                "job_id": hit['_id'],
                "score": hit['_score'],
                "source": hit['_source'],
                "highlight": hit.get('highlight', {})
            }
            results.append(result)
        
        return {
            "status": "success",
            "total": total,
            "results": results,
            "query": query_text
        }
        
    except Exception as e:
        error_msg = f"Elasticsearch搜索失败: {str(e)}"
        print(f"❌ {error_msg}")
        return {"status": "error", "message": error_msg}


# Create your views here.

class JobpostingAPIView(APIView):
    """
    🏢 招聘职位信息处理API - RAG系统的数据入口
    
    核心功能：
    1. 从Django数据库获取所有招聘职位信息
    2. 使用优化的语义分块策略处理职位文本
    3. 生成高质量的向量表示
    4. 将向量数据存储到Milvus向量数据库
    
    工作流程：
    数据获取 → 语义分块 → 向量化 → 向量存储
    
    这是整个RAG简历推荐系统的基础环节，为后续的语义搜索和智能匹配提供数据支撑。
    """
    
    def get(self, request):
        """
        🎯 GET请求处理 - 执行完整的职位数据向量化流程
        
        处理步骤：
        1. 从数据库加载所有职位信息
        2. 构建结构化的职位数据
        3. 应用优化的语义分块策略
        4. 批量向量化处理
        5. 存储到Milvus向量数据库
        
        Returns:
            Response: API响应，成功时返回"OK"
        """
        
        print("🚀 开始处理招聘职位数据向量化...")
        
        # 📊 Step 1: 数据库数据获取
        print("📊 Step 1: 从数据库获取职位信息...")
        jobposting_list = Jobposting.objects.all()
        total_jobs = len(jobposting_list)
        print(f"   获取到{total_jobs}个职位信息")
        
        if total_jobs == 0:
            print("⚠️  警告：数据库中没有职位信息")
            return Response({"status": "warning", "message": "没有找到职位数据"})
        
        # 🏗️ Step 2: 数据结构化转换
        print("🏗️  Step 2: 构建结构化职位数据...")
        job_list = []
        
        for index, jobposting in enumerate(jobposting_list):
            # 🏢 提取公司和城市信息
            company_name = jobposting.company.name if hasattr(jobposting.company, 'name') else str(jobposting.company)
            city_name = jobposting.city.name if hasattr(jobposting.city, 'name') else str(jobposting.city)
            
            # 🔧 构造完整的职位描述文本（增强版 - 包含公司信息）
            full_text = f"""
            岗位名称:{jobposting.title},公司名称:{company_name},职位描述:{jobposting.description},职位城市:{city_name},职位学历:{jobposting.education},职位薪资:{jobposting.money}
            """
            
            # 📦 创建结构化的职位信息对象（增强版 - 新增公司城市字段）
            jobposting_info = {
                "id": str(index),                    # 唯一标识符
                "full_text": full_text.strip(),      # 完整文本（兜底使用）
                "title": jobposting.title,           # 职位名称
                "money": jobposting.money,           # 薪资信息
                "working": jobposting.working,       # 工作经验要求
                "description": jobposting.description, # 职位描述
                "education": jobposting.education,   # 学历要求
                "city": jobposting.city,             # 工作城市（Django对象）
                "company_name": company_name,        # 🆕 公司名称（字符串）
                "city_name": city_name               # 🆕 城市名称（字符串）
            }
            job_list.append(jobposting_info)

        print(f"   ✅ 成功构建{len(job_list)}个结构化职位对象")
        
        # 🧩 Step 3: 应用优化的语义分块策略
        print("🧩 Step 3: 执行语义分块处理...")
        total_chunks = 0

        for job in job_list:
            # 🎯 使用新的优化分块策略替代原来的simple_sentence_split
            chunks = optimized_job_chunking(job)
            job["chunks"] = chunks
            total_chunks += len(chunks)
        
        print(f"   ✅ 语义分块完成，共生成{total_chunks}个高质量文本块")
        print(f"   📊 平均每个职位生成{total_chunks/len(job_list):.1f}个chunk")
        
        # 🤖 Step 4: 批量向量化处理
        print("🤖 Step 4: 执行向量化处理...")
        total_embeddings = 0
        
        for index, job in enumerate(job_list):
            chunks = job["chunks"]
            
            if chunks:  # 确保有文本块才进行向量化
                print(f"   🔄 处理职位 {index+1}/{len(job_list)}: {job.get('title', 'Unknown')}")
                embeddings = text_embeddings(chunks)
                job["embeddings"] = embeddings
                total_embeddings += len(embeddings)
            else:
                print(f"   ⚠️  职位 {index+1} 没有有效的文本块，跳过向量化")
                job["embeddings"] = []
        
        print(f"   ✅ 向量化完成，共生成{total_embeddings}个1024维向量")
        
        # 💾 Step 5: 存储到Milvus向量数据库
        print("💾 Step 5: 存储到Milvus向量数据库...")
        try:
            write_to_milvus(job_list)
            print("🎉 整个RAG数据处理流程完成！")
            
            # 📈 输出处理统计
            return Response({
                "status": "success",
                "message": "职位数据向量化完成",
                "statistics": {
                    "total_jobs": total_jobs,
                    "total_chunks": total_chunks, 
                    "total_embeddings": total_embeddings,
                    "avg_chunks_per_job": round(total_chunks/total_jobs, 2),
                    "vector_dimension": 1024
                }
            })
            
        except Exception as e:
            print(f"❌ Milvus存储失败: {str(e)}")
            return Response({
                "status": "error", 
                "message": f"向量存储失败: {str(e)}"
            })


class ElasticsearchSyncAPIView(APIView):
    """
    📤 Elasticsearch数据同步API - 混合检索系统的数据准备
    
    核心功能：
    1. 从数据库获取所有职位信息
    2. 数据格式化和清洗
    3. 批量同步到Elasticsearch
    4. 创建优化的搜索索引
    
    使用场景：
    - 初始化Elasticsearch索引
    - 数据增量同步
    - 数据结构更新
    """
    
    def get(self, request):
        """
        GET /sync-to-elasticsearch/
        
        执行数据同步到Elasticsearch的完整流程：
        1. 获取数据库中的职位数据
        2. 构建结构化的职位信息
        3. 同步到Elasticsearch
        4. 返回同步结果统计
        
        Returns:
            Response: 同步结果，包含成功/失败统计
        """
        
        print("📤 开始执行Elasticsearch数据同步...")
        
        try:
            # 📊 Step 1: 数据库数据获取
            print("📊 Step 1: 从数据库获取职位信息...")
            jobposting_list = Jobposting.objects.all()
            total_jobs = len(jobposting_list)
            print(f"   获取到{total_jobs}个职位信息")
            
            if total_jobs == 0:
                return Response({
                    "status": "warning", 
                    "message": "数据库中没有职位数据",
                    "total_jobs": 0
                })
            
            # 🏗️ Step 2: 数据结构化转换
            print("🏗️ Step 2: 构建结构化职位数据...")
            job_list = []
            
            for index, jobposting in enumerate(jobposting_list):
                try:
                    # 🏢 提取公司和城市信息
                    company_name = jobposting.company.name if hasattr(jobposting.company, 'name') else str(jobposting.company)
                    city_name = jobposting.city.name if hasattr(jobposting.city, 'name') else str(jobposting.city)
                    
                    # 🔧 构造完整的职位描述文本
                    full_text = f"""
                    岗位名称:{jobposting.title},公司名称:{company_name},职位描述:{jobposting.description},职位城市:{city_name},职位学历:{jobposting.education},职位薪资:{jobposting.money}
                    """
                    
                    # 📦 创建结构化的职位信息对象（ES版本）
                    jobposting_info = {
                        "id": str(jobposting.id),           # 使用数据库ID
                        "title": jobposting.title,
                        "company_name": company_name,
                        "city_name": city_name,
                        "money": jobposting.money,
                        "description": jobposting.description,
                        "education": jobposting.education,
                        "working": jobposting.working,
                        "type": jobposting.type,
                        "label": getattr(jobposting, 'label', jobposting.title),
                        "browse_number": jobposting.browse_number,
                        "collect_number": jobposting.collect_number,
                        "deliver_number": jobposting.deliver_number,
                        "full_text": full_text.strip()
                    }
                    
                    job_list.append(jobposting_info)
                    
                except Exception as e:
                    print(f"⚠️ 处理职位{index}时出错: {str(e)}")
                    continue
            
            print(f"   ✅ 数据结构化完成，有效职位{len(job_list)}个")
            
            # 📤 Step 3: 同步到Elasticsearch
            print("📤 Step 3: 同步数据到Elasticsearch...")
            sync_result = sync_jobs_to_elasticsearch(job_list)
            
            # 🎉 返回最终结果
            if sync_result["status"] == "success":
                return Response({
                    "status": "success",
                    "message": "数据同步到Elasticsearch完成",
                    "sync_result": sync_result,
                    "elasticsearch_info": {
                        "index_name": "job_positions",
                        "total_documents": sync_result["success_count"],
                        "searchable": True
                    }
                })
            else:
                return Response({
                    "status": "error",
                    "message": "Elasticsearch同步失败",
                    "error_details": sync_result.get("message", "未知错误")
                })
                
        except Exception as e:
            error_msg = f"数据同步过程中发生错误: {str(e)}"
            print(f"❌ {error_msg}")
            return Response({
                "status": "error",
                "message": error_msg
            })


class HybridSearchAPIView(APIView):
    """
    🔍 混合检索API - 结合ES和Milvus的智能搜索
    
    核心功能：
    1. ES关键词检索（稀疏检索）
    2. Milvus语义检索（稠密检索）
    3. 结果融合和排序
    4. 返回综合推荐结果
    """
    
    def post(self, request):
        """
        POST /hybrid-search/
        
        Body: {
            "query": "Java开发工程师 北京",
            "size": 10
        }
        
        执行混合检索：
        1. 同时进行ES关键词搜索和Milvus语义搜索
        2. 结果融合和去重
        3. 返回排序后的推荐职位
        
        Returns:
            Response: 混合检索结果
        """
        
        # 获取查询参数
        query_text = request.data.get("query", "")
        size = request.data.get("size", 10)
        
        if not query_text:
            return Response({
                "status": "error",
                "message": "查询文本不能为空"
            })
        
        print(f"🔍 开始混合检索: '{query_text}'")
        
        try:
            # 📊 Step 1: ES关键词检索
            print("📊 Step 1: 执行Elasticsearch关键词检索...")
            es_results = search_jobs_in_elasticsearch(query_text, size=size*2)  # 获取更多结果用于融合
            
            if es_results["status"] != "success":
                return Response({
                    "status": "error", 
                    "message": f"ES检索失败: {es_results.get('message', '未知错误')}"
                })
            
            # 🧠 Step 2: Milvus语义检索（这里需要先向量化查询文本）
            print("🧠 Step 2: 执行Milvus语义检索...")
            # TODO: 实现向量化查询文本 + Milvus检索
            # 这部分需要调用text_embeddings函数对query_text进行向量化
            # 然后在Milvus中搜索相似向量
            
            # 🔄 Step 3: 结果融合
            print("🔄 Step 3: 融合检索结果...")
            # TODO: 实现ES和Milvus结果的融合算法
            
            # 目前先返回ES检索结果
            final_results = es_results["results"][:size]
            
            return Response({
                "status": "success",
                "query": query_text,
                "total_found": es_results["total"],
                "returned_count": len(final_results),
                "results": final_results,
                "search_info": {
                    "es_search": True,
                    "milvus_search": False,  # TODO: 实现后改为True
                    "hybrid_fusion": False   # TODO: 实现后改为True
                }
            })
            
        except Exception as e:
            error_msg = f"混合检索失败: {str(e)}"
            print(f"❌ {error_msg}")
            return Response({
                "status": "error",
                "message": error_msg
            })


